/* ----------------------------------------------------------------------
 *	Shadow Quest
 *	Copyright (C) 2009 Prageeth Silva <prageeth@thenewcoders.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  ----------------------------------------------------------------------
 */
package units;

import java.util.Random;
import java.util.ArrayList;

import map.Item;
import map.MapEntity;
import map.World;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.util.pathfinding.Mover;
import org.newdawn.slick.util.pathfinding.Path;

import sq.FontLoader;
import sq.ImageLoader;
import sq.LoadScript;
import sq.game.Cheats;
import sq.game.Quest;
import sq.game.Team;
import sq.game.Team.Diplomacy;
import units.villagers.Villager;

public abstract class Unit extends MapEntity implements Mover {

    /**
     *
     */
    private static final long serialVersionUID = 1L;
    protected static String RESOURCE_PATH = "assets/units/";
    protected static String SCRIPT_CREATE = "unit.lua";
    protected static String SCRIPT_NAME = "script.lua";
    private static final String FUNC_CREATE = "create";
    private static final String FUNC_INIT = "init";
    public static int DEFAULT_WIDTH = 72, DEFAULT_HEIGHT = 72;
    // default speed of the unit
    public static double DEFAULT_SPEED = 0.25f;
    // default max HP
    public static int DEFAULT_MAX_HP = 100;
    // the default idling value
    public static int IDLE_TIME = 7000;
    // the time the dialog is shown for in milliseconds.
    public static int DIALOG_INTERVAL = 2000;
    // the duration as show the dead animation.
    public static int SHOW_DEAD = 7000;
    // the proximity for picking up items and attacking
    public static double CLOSE_PROXIMITY = 50;
    // the proximity for show dialog or monster attract
    public static double VISIBLITY = 150;
    // the proximity for archers to attack
    public static double PROJECTILE_RANGE = 150;
    // miss duration to be shown
    private final static int MISS_DURATION = 1000, MISS_HEIGHT = 50;
    // default width of the name/health bar
    private final static int DEFAULT_BAR_WIDTH = 60;
    // the number of times the teleport animation should be displayed for each teleport action.
    private final static int TELEPORT_REPEATS = 2;
    // the animation frame interval
    public final static int FRAME_FAST_INTERVAL = 100;
    // the animation frame interval
    public final static int FRAME_SLOW_INTERVAL = 150;
    // the display height of the health bar
    private final static double HEALTH_BAR_HEIGHT = 5;
    // the border size of the health bar
    private final static double HEALTH_BAR_BORDER = 1;

    // the type used to define the direction the unit
    public enum Direction {

        LEFT, RIGHT, UP, DOWN
    }
    // the attack type of the unit

    public enum AttackType {

        NONE, UNATTACKABLE, GROUND, ARCHER, AIR
    }
    // the attack type of the unit

    public enum Race {

        HUMANS, MONSTERS
    }
    // A universal count used to keep record of the units created.
    private static int unitsCount;
    private static Random rnd;											// random number generator
    private Animation disp, left, right, up, down;						// the display image/animation
    private Animation stnLeft, stnRight, stnUp, stnDown;				// the display image/animation
    private Animation upgLeft, upgRight, upgUp, upgDown;				// this display animations when an upgrade is done to the unit
    private Animation upgStnLeft, upgStnRight, upgStnUp, upgStnDown;	// this display animations when an upgrade is done to the unit
    private Animation attackLeft, upgAttackLeft;						// normal attack animations
    private Animation attackRight, upgAttackRight;						// upgraded attack animations
    private Animation dieRight, upgDieRight, dieLeft, upgDieLeft;		// this is displayed when the unit dies.
    private Animation idling, upgIdling;								// this is displayed when the unit dies.
    private Animation addon, teleport;									// this is some addon animation to be rendered over the unit
    private boolean upgradable, upgrade, usingUpg, usingAddon;			// checks if the unit has been upgraded, show addon.
    private boolean autoHeal, moving, killable;					// whether auto healing is allowed
    private boolean showUnitDialogue, dialogShown;						// flags used to show dialog.
    private boolean weapon, updateAni, appear;							// flag used to check if an auto weapon change had occurred.
    private boolean alive, teleporting, attacking, dieing;				// whether the unit is alive, teleporting, attacking
    private int id, teleportCount, restrictedTerrain;					// a universal unique id for each unit.
    private int damage, rangeDamage, cool, maxCool, defence;			// hit-points / health, damage, cool down rate
    private int dialogTimer, idleTimer, idleMax;						// the timer showing the dialog. invoking the idle action
    private int deadTimer;												// used for auto healing
    private double tempX, tempY;							// position and speed of the unit
    private double targetX, targetY;
    private boolean pursuingTarget, targetReached;
    private double hp, maxHP, autoHealRate, autoHealTimer;
    private double xp, maxXP;
    private int team;
    private int level, maxLevel;
    private boolean revive, reviveTeleport;
    private double reviveX, reviveY;										// the name of the unit
    private Direction direction;										// the direction which the unit is looking
    private AttackType attackType;										// the attack type of the unit
    private Race race;
    private String dialog;												// the dialog box to show
    private ArrayList<Item> inventory;										// the inventory.
    private ArrayList<Integer> misses;
    private ArrayList<Integer> missDurations;
    private Unit interactingUnit;
    private ArrayList<Unit> interactedBy;
    private LoadScript script;
    private String sPath;

    public static void init()
            throws SlickException {

        Unit.rnd = new Random();
        Unit.unitsCount = 0;
        
//		FlyingMonster.initFlyingMonster();

    }

    /** Create a new Unit object (overload).
     * @param unitName The display name of the unit.
     * @param maxHP The maximum HP of the unit.
     */
    public Unit(World world, int team)
            throws SlickException {

        super(world);

        this.setInventory(new ArrayList<Item>());
        this.setMissDurations(new ArrayList<Integer>());
        this.setMisses(new ArrayList<Integer>());
        this.setUnitInteractedBy(new ArrayList<Unit>());
        this.setId(unitsCount++);

        this.setTeam(team);
        this.setCoolDown(0);
        this.setIdleTimer(0);
        this.setAutoHealTimer(0);
        this.setDeadTimer(SHOW_DEAD);
        this.setReviving(false);

        this.setInteractingUnit(null);
        this.setRestrictedTerrain(World.TERRAIN_ANY);
        this.setAttackType(AttackType.UNATTACKABLE);

        this.setAlive(true);
        this.setTeleportingStatus(false);
        this.resetTeleportCount();
        this.setUpgradeStatus(false);
        this.setAttackingStatus(false);
        this.setAddonStatus(false);
        this.setDieingStatus(false);
        this.setUsingUprageStatus(false);
        this.setAutoWeaponChange(false);
        this.setUpdateAnimation(true);
        this.setAutoHealingStatus(false);
        this.setMoving(false);
        this.setAppearing(false);
        this.setOnMap(true);
        this.setUpgradable(false);
        this.setKillable(true);
        this.setTargetReached(false);
        this.setPursuingTarget(false);

        this.setFrameWidth(DEFAULT_WIDTH);
        this.setFrameHeight(DEFAULT_HEIGHT);
        this.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); //TODO

        // TODO : add the load script
        this.createFromScript();

    }

    /** Update the player's state for a frame.
     * @param world A reference to the world.
     * @param input The input object, used to take the keyboard inputs.
     * @param delta Time passed since last frame (milliseconds).
     */
    public void update(Input input, int delta) {

        if (this.isAlive()) {
            if (!this.isDieing() && !this.isAttacking()) {
                if (this.isAutoWeaponChanged()) {
                    this.setAutoWeaponChange(false);
                    this.setUsingUprageStatus(true);
                }
            }
            if (this.isPursuingTarget() && this.isReached()) {
                this.setTargetReached(true);
            }
        } else {
            this.setDeadTimer(this.getDeadTimer() - delta);
        }

        if (this.isAutoHealing()) {
            this.autoHeal(delta);
        }
        if (this.isUpdateAnimationSet()) {
            this.getDisplayAnimation().update(delta);
        }
        if (this.isUsingAddon()) {
            this.getAddonAnimation().update(delta);
        }
        if (this.isAppearing()) {
            this.makeEntrance();
        }
        if (this.getDeadTimer() <= 0) {
            this.setOnMap(false);
        }

        if (this.isAttacking() && this.getInteractingUnit() != null) {
            Unit unit = this.getInteractingUnit();
            double dist2 = calculateDistanceSqrd(unit.getX(), unit.getY(), this.getX(), this.getY());
            this.setInteractingUnit((unit.isAlive() && this.isInRange(dist2)) ? unit : null);
            if (this.getInteractingUnit() == null) {
                this.setAttackingStatus(false);
            }
        }

        this.updateMisses(delta);

    }

    protected void updateFromScript(Input input, int delta) {
        this.getScript().runUpdateScriptFunction(this, input, delta);
    }

    /** Loads all the images for this unit.
     * @param dirPath The path of the directory where the images are in (including the following "/").
     * @throws SlickException 
     */
    public void finalizeLoading()
            throws SlickException {
        this.setHP(this.getMaxHP());
        this.loadAllImages();
        this.standInRandomDirection();
    }

    /** Loads all the images for this unit.
     * @param dirPath The path of the directory where the images are in (including the following "/").
     * @throws SlickException 
     */
    protected void loadAllImages()
            throws SlickException {
        this.loadImages(this.getDirectoryPath());
        if (this.isUpgradable()) {
            this.loadUpgradeImages(this.getDirectoryPath());
        }
    }

    /** Loads all the common images for this unit.
     * @param dirPath The path of the directory where the images are in (including the following "/").
     * @throws SlickException 
     */
    private void loadImages(String dirPath)
            throws SlickException {

        String stand = "stn_";
        Image img;

        img = ImageLoader.getLoadedImage(dirPath + "die.png");
        this.setDieLeftAnimation(img);
        this.setDieRightAnimation(img.getFlippedCopy(true, false));
        this.setIdlingAnimation(ImageLoader.getLoadedImage(dirPath + "idle.png"));

        img = ImageLoader.getLoadedImage(dirPath + "left.png");
        this.setLeftAnimation(img);
        this.setRightAnimation(img.getFlippedCopy(true, false));
        this.setUpAnimation(ImageLoader.getLoadedImage(dirPath + "up.png"));
        this.setDownAnimation(ImageLoader.getLoadedImage(dirPath + "down.png"));

        img = ImageLoader.getLoadedImage(dirPath + stand + "left.png");
        this.setStandLeftAnimation(img);
        this.setStandRightAnimation(img.getFlippedCopy(true, false));
        this.setStandUpAnimation(ImageLoader.getLoadedImage(dirPath + stand + "up.png"));
        this.setStandDownAnimation(ImageLoader.getLoadedImage(dirPath + stand + "down.png"));

        img = ImageLoader.getLoadedImage(dirPath + "attack_left.png");
        this.setAttackLeftAnimation(img);
        this.setAttackRightAnimation(img.getFlippedCopy(true, false));

        this.setTeleportAnimation(ImageLoader.getLoadedImage("assets/units/teleport.png"));

    }

    /** Loads all the upgrade images for this unit.
     * @param dirPath The path of the directory where the images are in (including the following "/").
     * @throws SlickException 
     */
    private void loadUpgradeImages(String dirPath)
            throws SlickException {
        String upg = "upgrade_", stand = "stn_";
        Image img;

        img = ImageLoader.getLoadedImage(dirPath + upg + "die.png");
        this.setUpgradeDieLeftAnimation(img);
        this.setUpgradeDieRightAnimation(img.getFlippedCopy(true, false));
        this.setUpgradeIdlingAnimation(ImageLoader.getLoadedImage(dirPath + upg + "idle.png"));

        img = ImageLoader.getLoadedImage(dirPath + upg + "left.png");
        this.setUpgradeLeftAnimation(img);
        this.setUpgradeRightAnimation(img.getFlippedCopy(true, false));
        this.setUpgradeUpAnimation(ImageLoader.getLoadedImage(dirPath + upg + "up.png"));
        this.setUpgradeDownAnimation(ImageLoader.getLoadedImage(dirPath + upg + "down.png"));

        img = ImageLoader.getLoadedImage(dirPath + upg + stand + "left.png");
        this.setUpgradeStandLeftAnimation(img);
        this.setUpgradeStandRightAnimation(img.getFlippedCopy(true, false));
        this.setUpgradeStandUpAnimation(ImageLoader.getLoadedImage(dirPath + upg + stand + "up.png"));
        this.setUpgradeStandDownAnimation(ImageLoader.getLoadedImage(dirPath + upg + stand + "down.png"));

        img = ImageLoader.getLoadedImage(dirPath + upg + "attack_left.png");
        this.setUpgradeAttackLeftAnimation(img);
        this.setUpgradeAttackRightAnimation(img.getFlippedCopy(true, false));

    }

    /** Render the unit at the given position on the map.
     * @param g The Slick graphics object, used for drawing.
     * @param cameraOffsetX The number of pixels the world has been offset to in the x-direction (pixels).
     * @param cameraOffsetY The number of pixels the world has been offset to in the y-direction (pixels).
     */
    public void render(Graphics g, double cameraOffsetX, double cameraOffsetY) {

        g.fillRect((float)(this.getX() - cameraOffsetX - this.getWidth() / 2), (float)(this.getY() - cameraOffsetY - this.getHeight() / 2), (float)this.getWidth(), (float)this.getHeight());

        if (this.isAlive()) {
            if (this.isAppearing()) {
                this.getDisplayAnimation().draw((float)(this.getX() - cameraOffsetX - this.getDisplayAnimation().getCurrentFrame().getWidth() / 2), (float)(this.getY() - cameraOffsetY - this.getDisplayAnimation().getCurrentFrame().getHeight() + this.getFrameHeight()));
            } else {
                this.getDisplayAnimation().draw((float)(this.getX() - cameraOffsetX - this.getDisplayAnimation().getCurrentFrame().getWidth() / 2), (float)(this.getY() - cameraOffsetY - this.getDisplayAnimation().getCurrentFrame().getHeight() / 2));
            }
            if (this.isUsingAddon()) {
                this.getAddonAnimation().draw((float)(this.getX() - cameraOffsetX - this.getWidth() / 2), (float)(this.getY() - cameraOffsetY - this.getHeight() / 2));
            }
        } else if (this.isTeleporting()) {
            this.getDisplayAnimation().draw((float)(this.getX() - cameraOffsetX - this.getDisplayAnimation().getCurrentFrame().getWidth() / 2), (float)(this.getY() - cameraOffsetY - this.getDisplayAnimation().getCurrentFrame().getHeight() + this.getFrameHeight()));
        } else if (this.isOnMap()) {
            Image img = this.getBonesImage();
            if (this.getDirection() == Direction.LEFT) {
                img = img.getFlippedCopy(true, false);
            }
            img.setAlpha(this.getDeadTimer() / SHOW_DEAD);
            img.draw((float)(this.getX() - cameraOffsetX - img.getWidth() / 2), (float)(this.getY() - cameraOffsetY - img.getHeight() / 2));
        }

    }

    /** Renders the unit's display name.
     * @param g The current Slick graphics context.
     */
    public void renderDisplayNameAndHP(Graphics g, double cameraOffsetX, double cameraOffsetY) {

        Quest q = this.getWorld().getQuest();
        g.setFont(FontLoader.getFontSize11());
        double offset = 6, uhp = this.getHP() / this.getMaxHP();
        double textWidth = g.getFont().getWidth(this.getName());
        double width = DEFAULT_BAR_WIDTH;
        double textHeight = g.getFont().getHeight(this.getName());
        double x = this.getX() - cameraOffsetX - width / 2, xname = this.getX() - cameraOffsetX - (textWidth + offset) / 2;
        double y = this.getY() - cameraOffsetY - (this.getFrameHeight() / 2) - HEALTH_BAR_HEIGHT - (1.5f * offset);
        Color clr;

        if (this.isAlly(q.getCurrentPlayer())) {
            clr = FontLoader.getAllyBgColor();
        } else if (this.isEnemy(q.getCurrentPlayer())) {
            clr = FontLoader.getMonesterBgColor();
        } else if (this.isEnemy(q.getCurrentPlayer())) {
            // TODO make new colour
            clr = FontLoader.getDisplayBgColor();
        } else {
            clr = FontLoader.getDisplayBgColor();
        }
        g.setColor(clr);
        g.fillRect((float)xname, (float)(y - offset / 2), (float)(textWidth + offset), (float)textHeight);
        g.setColor(FontLoader.getDisplayBgColor());
        g.fillRect((float)(x + HEALTH_BAR_BORDER), (float)(y + textHeight + HEALTH_BAR_BORDER), (float)(width - 2 * HEALTH_BAR_BORDER), (float) HEALTH_BAR_HEIGHT);
        g.setColor(new Color((float)(1 - uhp), (float)uhp, 0f, 1f));
        g.fillRect((float)(x + HEALTH_BAR_BORDER), (float)(y + textHeight + HEALTH_BAR_BORDER), (float)((width - 2 * HEALTH_BAR_BORDER) * uhp), (float)(HEALTH_BAR_HEIGHT));
        g.setColor(Color.white);
        g.drawString(this.getName(), (float)(xname + offset / 2), (float)(y - offset / 2));
        g.drawImage(ImageLoader.getBarHP(), (float)x, (float)(y + textHeight));
    }

    /** Renders the unit's display name only.
     * @param g The current Slick graphics context.
     */
    public void renderDisplayNameOnly(Graphics g, double cameraOffsetX, double cameraOffsetY) {

        Quest q = this.getWorld().getQuest();
        g.setFont(FontLoader.getFontSize11());
        double offset = 6;
        double textWidth = g.getFont().getWidth(this.getName());
        double textHeight = g.getFont().getHeight(this.getName());
        double x = (float) (this.getX() - cameraOffsetX - textWidth / 2);
        double y = (float) (this.getY() - cameraOffsetY - (this.getFrameHeight() / 2) - HEALTH_BAR_HEIGHT - offset);
        Color clr;

        if (this.isAlly(q.getCurrentPlayer())) {
            clr = FontLoader.getAllyBgColor();
        } else if (this.isEnemy(q.getCurrentPlayer())) {
            clr = FontLoader.getMonesterBgColor();
        } else if (this.isEnemy(q.getCurrentPlayer())) {
            // TODO make new colour
            clr = FontLoader.getDisplayBgColor();
        } else {
            clr = FontLoader.getDisplayBgColor();
        }
        g.setColor(clr);
        g.fillRect((float)x, (float)y, (float)(textWidth + offset), (float)textHeight);
        g.setColor(Color.white);
        g.drawString(this.getName(), (float)(x + offset / 2), (float)y);
    }

    /** Renders the unit's display name.
     * @param g The current Slick graphics context.
     */
    public void renderDialogue(Graphics g, double cameraOffsetX, double cameraOffsetY) {
        if (this.isShowUnitDialogue()) {
            double offset = 6;
            double textWidth = g.getFont().getWidth(this.getDialogue()) + offset;
            double width = Math.max(DEFAULT_BAR_WIDTH, textWidth);
            double height = g.getFont().getHeight(this.getName());
            double x = this.getX() - cameraOffsetX - width / 2, y = this.getY() - cameraOffsetY - (this.getHeight() / 2) - 2 * (height + offset);
            g.setColor(FontLoader.getDialogueBgColor());
            g.fillRect((float)x, (float)y, (float)width, (float)height);
            g.setColor(Color.white);
            g.drawString(this.getDialogue(), (float)(x + (width - textWidth + offset) / 2), (float)y);
        }
    }

    /** Move the unit to a point, only if the point is valid (uses collision detection).
     * @param x The X coordinate (pixels).
     * @param y The Y coordinate (pixels).
     */
    public void move(double x, double y) {

              // move the unit, only if the movement is valid
        if (!this.getWorld().isBlocked(this.getX(), this.getY()+y, this)) {
                this.moveY(y);
                if (y < 0) {
                        this.lookUp(true);
                } else if (y > 0) {
                        this.lookDown(true);

                }
        }
        if (!this.getWorld().isBlocked(this.getX()+x, this.getY(), this)) {
                this.moveX(x);
                if (x < 0) {
                        this.lookLeft(true);
                } else if (x > 0) {
                        this.lookRight(true);
                }
        }

        boolean move = (x != 0 || y != 0);
        if (this.isMoving() && !move) { this.standInDefaultDirection(); }
        this.setMoving(move);
        this.setAttackingStatus(false);


    }

    /** Move the unit relative to the current position in the horizontal.
     * @param dir_x The number of pixels in the horizontal plane  (pixels).
     */
    private void moveX(double x) {
        this.moveTo(this.getX() + x, this.getY());
    }

    /** Move the unit relative to the current position in the horizontal.
     * @param dir_x The number of pixels in the horizontal plane  (pixels).
     */
    private void moveY(double y) {
        this.moveTo(this.getX(), this.getY() + y);
    }

    /** Turn the unit to the default direction.
     */
    protected void lookInDefaultDirection() {
        if (this.getDirection() == Direction.LEFT) {
            this.lookLeft();
        } else if (this.getDirection() == Direction.RIGHT) {
            this.lookRight();
        } else if (this.getDirection() == Direction.UP) {
            this.lookUp();
        } else if (this.getDirection() == Direction.DOWN) {
            this.lookDown();
        }
    }

    /** Turn the unit to the default direction.
     */
    public void standInDefaultDirection() {
        if (this.getDirection() == Direction.LEFT) {
            this.standLeft();
        } else if (this.getDirection() == Direction.RIGHT) {
            this.standRight();
        } else if (this.getDirection() == Direction.UP) {
            this.standUp();
        } else if (this.getDirection() == Direction.DOWN) {
            this.standDown();
        }
    }

    /** Turn the unit to the left.
     */
    protected void lookLeft() {
        this.setIdleTimer(0);
        this.lookLeft(false);
    }

    /** Turn the unit to the right.
     */
    protected void lookRight() {
        this.setIdleTimer(0);
        this.lookRight(false);
    }

    /** Turn the unit to the left.
     */
    protected void lookUp() {
        this.setIdleTimer(0);
        this.lookUp(false);
    }

    /** Turn the unit to the left.
     */
    protected void lookDown() {
        this.setIdleTimer(0);
        this.lookDown(false);
    }

    /** Turn the unit to the left and make the unit stand.
     */
    private void standLeft() {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeStandLeftAnimation() : this.getStandLeftAnimation());
        this.setDirection(Direction.LEFT);
    }

    /** Turn the unit to the right.
     */
    private void standRight() {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeStandRightAnimation() : this.getStandRightAnimation());
        this.setDirection(Direction.RIGHT);
    }

    /** Turn the unit to the left.
     */
    private void standUp() {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeStandUpAnimation() : this.getStandUpAnimation());
        this.setDirection(Direction.UP);
    }

    /** Turn the unit to the left.
     */
    private void standDown() {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeStandDownAnimation() : this.getStandDownAnimation());
        this.setDirection(Direction.DOWN);
    }

    /** Turn the unit to the left.
     * @param walking If the unit is walking in the direction.
     */
    private void lookLeft(boolean walking) {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeLeftAnimation() : this.getLeftAnimation(), walking);
        this.setDirection(Direction.LEFT);
    }

    /** Turn the unit to the right.
     * @param walking If the unit is walking in the direction.
     */
    private void lookRight(boolean walking) {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeRightAnimation() : this.getRightAnimation(), walking);
        this.setDirection(Direction.RIGHT);
    }

    /** Turn the unit to the left.
     * @param walking If the unit is walking in the direction.
     */
    private void lookUp(boolean walking) {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeUpAnimation() : this.getUpAnimation(), walking);
        this.setDirection(Direction.UP);
    }

    /** Turn the unit to the left.
     * @param walking If the unit is walking in the direction.
     */
    private void lookDown(boolean walking) {
        this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeDownAnimation() : this.getDownAnimation(), walking);
        this.setDirection(Direction.DOWN);
    }

    /** Turn the unit to the right or left randomly.
     */
    public void standInRandomDirection() {
        if ((Math.abs(getRandomInt() % 2)) == 0) {
            this.standLeft();
        } else {
            this.standRight();
        }
    }

    /** Make the unit HP go to MaxHP.
     */
    public void restoreFullHP() {
        this.setHP(this.getMaxHP());
    }

    /** Make the unit be reborn.
     */
    public void revive() {
        this.getUnitDieingAnimation().stop();
        //this.restoreFullHP();
        this.setAlive(true);
        this.standLeft();
    }

    /** Make the unit die, and drop all the items in the inventory at that spot on the map.
     */
    private void die()
    {
        this.setDisplayAnimation(this.getUnitDieingAnimation());
        this.getUnitDieingAnimation().restart();
        this.setDieingStatus(true);
        this.setAttackingStatus(false);
        if (Unit.isPlayer(this))
        {
            this.getWorld().getQuest().addMsgToStatus(this.getName() + " is dead.");
        }
        dropAllInventoryItems();
        for (Unit u : this.getUnitInteractedBy())
        {
            u.setInteractingUnit(null);
        }
        this.getUnitInteractedBy().clear();
        this.getWorld().getDeadUnitsOnly().add(this);
        this.getWorld().getAllOtherUnits().remove(this);
        this.getWorld().getAllUnits().remove(this);
    }

    protected void checkDieing(boolean teleport, double teleportX, double teleportY) {
        if (this.getDisplayAnimation().isStopped()) {
            this.setAlive(false);
            this.setDieingStatus(false);
            if (teleport) {
                this.teleport(teleportX, teleportY);
            }
        }
    }

    /** Make the unit be reborn.
     */
    public Animation getUnitDieingAnimation() {
        if (this.getDirection() == Direction.LEFT) {
            return this.isUsingUpgrade() ? this.getUpgradeDieLeftAnimation() : this.getDieLeftAnimation();
        } else {
            return this.isUsingUpgrade() ? this.getUpgradeDieRightAnimation() : this.getDieRightAnimation();
        }
    }

    /** Teleports the unit to a given point.
     * @param x The x coordinate of where the unit should be teleported to.
     * @param y The y coordinate of where the unit should be teleported to.
     */
    public void teleport(double x, double y) {
        if (!this.isTeleporting()) {
            this.getTeleportAnimation().restart();
            this.setDisplayAnimation(this.getTeleportAnimation());
            this.setTeleportingStatus(true);
        } else if (this.getTeleportAnimation().isStopped()) {
            this.incrementTeleportCount();
            if (this.getTeleportCount() == TELEPORT_REPEATS) {
                this.resetTeleportCount();
                this.setTeleportingStatus(false);
            } else if (this.getTeleportCount() == 1) {
                this.moveTo(x, y);
                this.getTeleportAnimation().restart();
            }
        }
    }

    /** Teleports the unit to make an entrance to the world.
     */
    // TODO
    public void makeEntrance() {
        if (!this.isAppearing()) {
            this.getTeleportAnimation().restart();
            this.setDisplayAnimation(this.getTeleportAnimation());
            this.setAppearing(true);
        } else if (this.getTeleportAnimation().isStopped()) {
            this.setAppearing(false);
            this.standInRandomDirection();
        }
    }

    /** Increment the teleport count.
     */
    private void incrementTeleportCount() {
        this.setTeleportCount(this.getTeleportCount() + 1);
    }

    /** Reset the teleport count.
     */
    private void resetTeleportCount() {
        this.setTeleportCount(0);
    }

    /** Upgrades the unit.
     */
    public void upgrade() {
        if (this.isUpgradable()) {
            this.setUpgradeStatus(true);
            this.setUsingUprageStatus(true);
        }
    }

    /** Upgrades the unit.
     */
    private void downgrade() {
        this.setUpgradeStatus(false);
    }

    /** Adds the given item to the inventory.
     * @param item The item to be added.
     */
    public void pickUpItem(Item item) {

        item.getPickedUp();
        this.getWorld().getQuest().addMsgToStatus("Picked up " + item.getName() + ".");

        Item i = Item.searchItem(this, item.getItemType());
        if (i != null) {
            i.setValue(i.getValue() + item.getValue());
            i.getDuplicates().add(item);
        } else {
            item.setPickedUpStatus(true);
            this.getInventory().add(item);
        }

        // if the unit's HP is full, then increase the HP to the new MaxHP as well.
        if (this.getHP() == this.getMaxHP()) {
            this.setHP(this.getHP() + item.getHP());
        }
        this.setMaxHP(this.getMaxHP() + item.getHP());
        this.setMaxCoolDown(this.getMaxCoolDown() + item.getCoolDownRate());
        this.setDamage(this.getDamage() + item.getDamage());
        this.setSpeed(this.getSpeed() + item.getSpeed());
        this.setDefence(this.getDefence() + item.getDefence());
        if (item.getHealRate() > 0) {
            this.setAutoHealRate(this.getAutoHealRate() / item.getHealRate());
        }

        if (item.getItemType() != Item.ItemType.EXPLOSIVE) {
            if (Unit.isPlayer(this)) {
                this.addAttackType(item.getAttackType());
                this.setRangeDamage(this.getRangeDamage() + item.getRangeDamage());
            }
        }
        if (item.getItemType() == Item.ItemType.BOW) {
            this.upgrade();
        }

    }

    /** Removes all the items from the inventory.
     */
    private void dropAllInventoryItems() {

        Quest q = this.getWorld().getQuest();
        double len = this.getInventory().size();

        if (len == 1) {
            q.addMsgToStatus(this.getName() + " dropped " + this.getInventory().get(0).getName());
        } else if (len > 1) {
            q.addMsgToStatus(this.getName() + " dropped all the items.");
        }

        double sq = (float) Math.sqrt(len);
        for (int i = this.getInventory().size() - 1; i >= 0; i--) {
            double x = this.getX() + this.getInventory().get(i).getWidth() * (len - i) % sq;
            double y = this.getY() + this.getInventory().get(i).getWidth() * (len - i) % sq;
            this.dropInventoryItem(i, x, y);
        }

    }

    /** Removes the an item from the inventory.
     * @param item The item to be removed.
     * @param x The x coordinate as to where the item should be dropped (in pixels).
     * @param y The y coordinate as to where the item should be dropped (in pixels).
     */
    public void dropInventoryItem(Item item, double x, double y) {
        for (int i = this.getInventory().size() - 1; i >= 0; i--) {
            if (this.getInventory().get(i) == item) {
                this.dropInventoryItem(i, x, y);
            }
        }
    }

    /** Removes the an item from the inventory.
     * @param index The index of which the item to be removed is at.
     * @param x The x coordinate as to where the item should be dropped (in pixels).
     * @param y The y coordinate as to where the item should be dropped (in pixels).
     */
    public void dropInventoryItem(int index, double x, double y) {
        this.dropInventoryItem(index, x, y, true);
    }

    /** Removes the an item from the inventory.
     * @param index The index of which the item to be removed is at.
     * @param x The x coordinate as to where the item should be dropped (in pixels).
     * @param y The y coordinate as to where the item should be dropped (in pixels).
     */
    public void dropInventoryItem(int index, double x, double y, boolean dropDuplicates) {
        Item itemTop = this.getInventory().get(index);
        Item item = itemTop;
        int last = item.getDuplicates().size() - 1;
        if (!dropDuplicates && (item.getDuplicates().size() > 0)) {
            item = item.getDuplicates().get(last);
        }
        if (item.getItemType() == Item.ItemType.BOW) {
            this.downgrade();
        }
        if (this.getHP() == this.getMaxHP()) {
            this.setHP(this.getHP() - item.getHP());
        }
        this.setMaxHP(this.getMaxHP() - item.getHP());
        this.setMaxCoolDown(this.getMaxCoolDown() - item.getCoolDownRate());
        this.setDamage(this.getDamage() - item.getDamage());
        this.setSpeed(this.getSpeed() - item.getSpeed());
        this.setDefence(this.getDefence() - item.getDefence());
        if (item.getHealRate() > 0) {
            this.setAutoHealRate(this.getAutoHealRate() * item.getHealRate());
        }
        if (item.getItemType() != Item.ItemType.EXPLOSIVE) {
            this.setRangeDamage(this.getRangeDamage() - item.getRangeDamage());
        }
        this.removeAttackType(item.getAttackType());
        item.setOnMap(true);
        item.setPickedUpStatus(false);
        item.moveTo(x, y);
        if (!dropDuplicates && (item.getDuplicates().size() > 0)) {
            itemTop.setValue(itemTop.getValue() - item.getValue());
            itemTop.getDuplicates().remove(itemTop.getDuplicates().get(last));
        } else {
            this.getInventory().remove(this.getInventory().get(index));
        }
    }

    /** Removes the an item from the inventory, but does not drop it on the map.
     * @param item The item to be removed.
     * @param unit The unit which the item is given to.
     */
    public void giveItem(Item item, Unit unit) {
        for (int i = this.getInventory().size() - 1; i >= 0; i--) {
            if (this.getInventory().get(i).typeEquals(item)) {
                giveItem(i, unit);
            }
        }
    }

    /** Removes the an item from the inventory, but does not drop it on the map.
     * @param index The index of which the item to be removed is at.
     * @param unit The unit which the item is given to.
     */
    public void giveItem(int index, Unit unit) {
        this.giveItem(index, unit, true);
    }

    /** Removes the an item from the inventory, but does not drop it on the map.
     * @param index The index of which the item to be removed is at.
     * @param unit The unit which the item is given to.
     */
    public void giveItem(int index, Unit unit, boolean giveAll) {
        Item itemTop = this.getInventory().get(index);
        Item item = itemTop;
        int last = item.getDuplicates().size() - 1;
        if (giveAll && (item.getDuplicates().size() > 0)) {
            item = item.getDuplicates().get(last);
        }
        if (this.getHP() == this.getMaxHP()) {
            this.setHP(this.getHP() - item.getHP());
        }
        this.setMaxHP(this.getMaxHP() - item.getHP());
        this.setMaxCoolDown(this.getMaxCoolDown() - item.getCoolDownRate());
        this.setDamage(this.getDamage() - item.getDamage());
        this.setSpeed(this.getSpeed() - item.getSpeed());
        this.setDefence(this.getDefence() - item.getDefence());
        if (item.getHealRate() > 0) {
            this.setAutoHealRate(this.getAutoHealRate() * item.getHealRate());
        }
        if (item.getItemType() != Item.ItemType.EXPLOSIVE) {
            this.setRangeDamage(this.getRangeDamage() - item.getRangeDamage());
        }
        item.setPickedUpStatus(false);
        unit.getInventory().add(item);
        String text = this.getName() + " gave " + item.getName() + " to " + unit.getName() + ".";
        this.getWorld().getQuest().addMsgToStatus(text);
        if (giveAll) {
            this.getInventory().remove(this.getInventory().get(index));
        } else {
            itemTop.setValue(itemTop.getValue() - item.getValue());
            item.getDuplicates().remove(item.getDuplicates().get(last));
        }
    }

    /** Attack another unit
     * @param victim The victim of the attack.
     * @param dir The direction of attacking.
     */
    public void attack(Unit victim, Direction dir) {

        // check for god mode cheat
        if (Unit.isPlayer(victim)) {
            if (Cheats.isOnGodMode()) {
                return;
            }
        }

        boolean dead = false;
        if (Unit.isPlayer(this)) {
            this.getWorld().getQuest().addMsgToStatus(this.getName() + " is attacking " + victim.getName() + ".");
        }

        if (this.getCoolDown() == 0) {

            int mod = (this.isRangedAttacker()) ? this.getRangeDamage() : this.getDamage();
            int hit = Math.abs(getRandomInt() % mod);
            int def = (this.getDefence() > 0) ? Math.abs(getRandomInt()) % this.getDefence() : 0;
            int attack = hit - def;

            if (attack > 0) {
                dead = victim.getInjured(attack, this.getMaxCoolDown());
            } else {
                this.missHit(hit);
            }

            this.setCoolDown(this.getMaxCoolDown());

        }

        if (!dead) {
            this.setAttackingStatus(true);
            this.setDirection(dir);
            if (dir == Direction.LEFT) {
                this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeAttackLeftAnimation() : this.getAttackLeftAnimation());
            } else if (dir == Direction.RIGHT) {
                this.setDisplayAnimation(this.isUsingUpgrade() ? this.getUpgradeAttackRightAnimation() : this.getAttackRightAnimation());
            }
        } else {
            this.gainXP(victim);
            this.standInDefaultDirection();
        }

        this.setAttackingStatus(!dead);

    }

    /** Check if the unit is in visibility range or if it can be attacked.
     * @param Unit The Slick graphics object, used for drawing.
     * @param cameraOffsetX The number of pixels the world has been offset to in the x-direction (pixels).
     * @return Whether any attacks were initiated with the given unit.
     */
    protected boolean checkUnitToFight(Unit unit, int delta) {

        this.setAttackingStatus(false);

        if (unit.isAlive() && !unit.isDieing() && !this.isDieing() && isAttackAllowed(this, unit)) {

            double d_x = unit.getX() - this.getX(), d_y = unit.getY() - this.getY();

            double dx = (d_x < 0 ? unit.getMaxX() : unit.getMinX()) - this.getX();
            double dy = (d_y < 0 ? unit.getMaxY() : unit.getMinY()) - this.getY();
            double dist2 = calculateDistanceSqrd(dx, dy);

            boolean atk = false;

            if (this.isInRange(dist2)) {
                atk = true;
                this.attack(unit, dx < 0 ? Direction.LEFT : Direction.RIGHT);
                this.setInteractingUnit(unit);
                unit.getUnitInteractedBy().add(this);
                if (this.isRangedAttacker() && dist2 <= Unit.Squared(CLOSE_PROXIMITY)) {
                    this.setAutoWeaponChange(true);
                    this.setUsingUprageStatus(false);
                }
            }

            if (!atk) {
                if (this.isAttacking()) {
                    this.standInDefaultDirection();
                }
                this.setAttackingStatus(false);
                if (this.isAutoWeaponChanged()) {
                    this.setAutoWeaponChange(false);
                    this.setUsingUprageStatus(true);
                }
            }

            if (dist2 <= Unit.Squared(VISIBLITY) && !this.isAttacking()) {

                double dist;
                double dirx;
                double diry;

                Path path = this.getPath(this.getWorld(), unit.getX(), unit.getY());

                if (path != null && path.getLength() > 3) {

                    double halfTileX = this.getWorld().getMap().getTileWidth() / 2;
                    double halfTileY = this.getWorld().getMap().getTileHeight() / 2;

                    dx = path.getStep(path.getLength() - 1).getX() * this.getWorld().getMap().getTileWidth() - this.getX();
                    dy = path.getStep(path.getLength() - 1).getY() * this.getWorld().getMap().getTileHeight() - this.getY();

                    dx += (dx < 0 ? -halfTileX : halfTileX);
                    dy += (dx < 0 ? -halfTileY : halfTileY);

                    dist2 = calculateDistanceSqrd(dx, dy);

                }

                dist = (float) Math.sqrt(dist2);
                dirx = dx / dist * this.getSpeed();
                diry = dy / dist * this.getSpeed();

                this.setTempX(dirx);
                this.setTempY(diry);

            }

        }

        return this.isAttacking() || this.getTempX() != 0 || this.getTempY() != 0;

    }

    protected void gainXP(Unit victim) {
        double uxp = victim.getMaxHP() * 1f;
        this.gainXP(uxp);
    }

    protected void gainXP(double xp) {
        this.setXP(this.getXP() + xp);
        if (this.getXP() >= this.getMaxXP()) {
            this.setXP(this.getXP() - this.getMaxXP());
            this.levelUp();
        }
    }

    protected void levelUp() {
        this.setLevel(this.getLevel() + 1);
        if (this.getLevel() > this.getMaxLevel()) {
            this.setLevel(this.getMaxLevel());
        }
    }

    /** Checks if a given point on the map is occupied by this unit.
     * @param x The x coordinate.
     * @param y The y coordinate.
     */
    private void autoHeal(int delta) {
        if (this.getHP() < this.getMaxHP()) {
            if (!this.isAttacking() && this.isAlive() && !this.isTeleporting()) {
                this.setAutoHealTimer(this.getAutoHealTimer() + delta);
                if (this.getAutoHealTimer() >= this.getAutoHealRate()) {
                    this.setAutoHealTimer(0);
                    this.setHP(this.getHP() + 1);
                }
            }
        } else {
            this.setAutoHealTimer(0);
        }
    }

    /** Upgrade the attack type if valid
     * @param type The new attack type.
     */
    private void addAttackType(AttackType type) {
        switch (type) {
            case AIR:
                this.setAttackType(type);
                break;
            case ARCHER:
                if (this.getAttackType() != AttackType.AIR) {
                    this.setAttackType(type);
                }
                break;
            case GROUND:
                if (this.getAttackType() != AttackType.AIR && this.getAttackType() != AttackType.ARCHER) {
                    this.setAttackType(type);
                }
                break;
        }
    }

    /** Upgrade the attack type if valid
     * @param type The new attack type.
     */
    private void removeAttackType(AttackType type) {
        switch (type) {
            case ARCHER:
                this.setAttackType(AttackType.GROUND);
                break;
        }
    }

    /** Enable the rendering of the dialog.
     */
    public void showDialogue() {
        this.setShowUnitDialogue(true);
    }

    /** Disables the rendering of the dialog.
     */
    // TODO
    protected void hideDialogue() {
        this.setShowUnitDialogue(false);
    }

    /** Make the unit display the idling animation.
     * @param delta The time passed since the previous frame update (milliseconds).
     */
    protected void idle(int delta) {

        if (this.isMoving()) {
            this.standInDefaultDirection();
        }
        this.setMoving(false);

        this.setIdleTimer(this.getIdleTimer() + delta);
        if (this.getIdleTimer() >= this.getIdleMax()) {
            this.setIdleTimer(0);
            if (!this.isUsingAddon()) {
                if (this.isUsingUpgrade()) {
                    this.setDisplayAnimation(this.getUpgradeIdlingAnimation());
                    this.getUpgradeIdlingAnimation().restart();
                } else {
                    this.setDisplayAnimation(this.getIdlingAnimation());
                    this.getIdlingAnimation().restart();
                }
            }
        }
        boolean endIdle = (!this.isUsingUpgrade() && !this.getIdlingAnimation().isStopped() && this.getIdlingAnimation().getFrame() == this.getIdlingAnimation().getFrameCount() - 1)
                || (this.isUsingUpgrade() && !this.getUpgradeIdlingAnimation().isStopped() && this.getUpgradeIdlingAnimation().getFrame() == this.getUpgradeIdlingAnimation().getFrameCount() - 1);
        if (endIdle) {
            this.setIdleTimer(0);
            if (this.isUsingUpgrade()) {
                this.getUpgradeIdlingAnimation().stop();
            } else {
                this.getIdlingAnimation().stop();
            }
            this.standInRandomDirection();
        }
    }

    public Path getPath(World world, double x, double y) {

        int targetTileX = (int) (x / world.getMap().getTileWidth());
        int targetTileY = (int) (y / world.getMap().getTileHeight());
        int currentTileX = (int) (this.getX() / world.getMap().getTileWidth());
        int currentTileY = (int) (this.getY() / world.getMap().getTileHeight());

        Path path = world.getFinder().findPath(this, currentTileX, currentTileY, targetTileX, targetTileY);

        return path;

    }

    /** Unit comparison, checks if a this unit is the same as the given unit.
     * @param unit The comparing unit.
     */
    public boolean equals(Unit unit) {
        return this.getId() == unit.getId();
        //return (this == unit);
    }

    /** Calculates the square of the given value.
     * @param value The value to be squared.
     * @return The squared value.
     */
    public static double Squared(double value) {
        return (float) Math.pow(value, 2);
    }

    /** Calculates the distance between two points.
     * @param x1 The x coordinate of the first point (pixels).
     * @param y1 The y coordinate of the first point (pixels).
     * @param x2 The x coordinate of the second point (pixels).
     * @param y2 The y coordinate of the second point (pixels).
     * @return The distance (pixels).
     */
    public static double calculateDistanceSqrd(double x1, double y1, double x2, double y2) {
        return (float) (Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
    }

    /** Calculates the distance between two points.
     * @param x1 The horizontal distance (pixels).
     * @param y1 The vertical distance (pixels).
     * @return The distance (pixels).
     */
    public static double calculateDistanceSqrd(double dx, double dy) {
        return (float) (Math.pow(dx, 2) + Math.pow(dy, 2));
    }

    /** Checks if an attack is allowed, depending on the two units attack type.
     * @param attacker The unit that is attacking.
     * @param victim The unit that is being attacked.
     * @return True, if the attack is allowed, else false.
     */
    public static boolean isAttackAllowed(Unit attacker, Unit victim) {
        boolean allowed = attacker.isRangedAttacker();
        allowed |= (attacker.getAttackType() == AttackType.GROUND) && !victim.isFlyingUnit();
        return allowed && (victim.getAttackType() != AttackType.UNATTACKABLE);
    }

    /** Generate a new random number
     * @return a randomly generated number.
     */
    private static int getRandomInt() {
        return rnd.nextInt();
    }

    /** Generate a new random number
     * @return a randomly generated number.
     */
    public static double getRandomDouble() {
        return rnd.nextFloat();
    }

    /* Getters and Setters beyond this point */
    /** Set the unique id of the unit.
     * @param The unique id.
     */
    private void setId(int id) {
        this.id = id;
    }

    /** Get the unique id of the unit.
     * @return The unique id.
     */
    private int getId() {
        return this.id;
    }

    public final void setTeam(int team) {
        this.team = team;
        Team t = this.getWorld().getQuest().getTeams().get(team);
        if (t.getTeamLeader() == null)
        {
            t.setTeamLeader(this);
        }
    }

    public int getTeam() {
        return this.team;
    }

    protected String getDirectoryPath() {
        return this.sPath;
    }

    public void setDirectoryPath(String path) {
        sPath = path;
    }

    /** Get the display image of the unit.
     * @return The Display Image.
     */
    private Animation getDisplayAnimation() {
        return this.disp;
    }

    /** Set the display image of the unit.
     * @param image The Display Image.
     */
    public void setDisplayAnimation(Animation animation) {
        setDisplayAnimation(animation, true);
    }

    /** Set the display image of the unit.
     * @param image The Display Image.
     */
    private void setDisplayAnimation(Animation animation, boolean update) {
        this.disp = animation;
        if (update) {
            this.setIdleTimer(0);
        }
        this.setUpdateAnimation(update);
    }

    /** Get the health of the unit.
     * @return The Health value.
     */
    public double getHP() {
        return this.hp;
    }

    /** Set the health of the unit.
     * @param health The Health Value.
     */
    public void setHP(double hp) {
        this.hp = (hp < 0) ? 0 : hp;
    }

    /** Get the max health of the unit.
     * @return The maximum health value.
     */
    public double getMaxHP() {
        return this.maxHP;
    }

    /** Set the max health of the unit.
     * @param health The maximum health Value.
     */
    public void setMaxHP(double hp) {
        this.maxHP = (hp < 0) ? 0 : hp;
    }

    /** Get the direction of the unit.
     * @return The direction.
     */
    // TODO
    public Direction getDirection() {
        return this.direction;
    }

    /** Set the direction of the unit.
     * @param dir The direction.
     */
    // TODO
    public void setDirection(Direction dir) {
        this.direction = dir;
    }

    /** Get the damage of the unit.
     * @return The damage.
     */
    public int getDamage() {
        return this.damage;
    }

    /** Set the damage of the unit.
     * @param damage The damage.
     */
    public void setDamage(int damage) {
        this.damage = (damage < 0) ? 0 : damage;
    }

    /** Get the range damage of the unit.
     * @return The damage.
     */
    public int getRangeDamage() {
        return this.rangeDamage;
    }

    /** Set the range damage of the unit.
     * @param damage The damage.
     */
    public void setRangeDamage(int damage) {
        this.rangeDamage = (damage < 0) ? 0 : damage;
    }

    /** Get the cool down rate of the unit.
     * @return The cool down rate.
     */
    public int getCoolDown() {
        return this.cool;
    }

    /** Set the cool down rate of the unit.
     * @param rate The cool down rate.
     */
    public final void setCoolDown(int rate) {
        this.cool = (rate < 0) ? 0 : rate;
    }

    /** Get the maximum cool down rate of the unit.
     * @return The cool down rate.
     */
    public int getMaxCoolDown() {
        return this.maxCool;
    }

    /** Set the maximum cool down rate of the unit.
     * @param rate The cool down rate.
     */
    public void setMaxCoolDown(int rate) {
        this.maxCool = (rate < 0) ? 0 : rate;
    }

    /** Get the life status of the unit.
     * @return The life status.
     */
    public boolean isAlive() {
        return this.alive;
    }

    /** Set the life status of the unit.
     * @param alive The life status.
     */
    // TODO
    public final void setAlive(boolean alive) {
        this.alive = alive;
    }

    /** Get the player's teleporting animation.
     * @return The animation.
     */
    private Animation getTeleportAnimation() {
        return teleport;
    }

    /** Set the player's teleporting animation.
     * @param image The sprite image.
     */
    private void setTeleportAnimation(Image image) {
        this.teleport = new Animation(new SpriteSheet(image, 150, 375), 200);
        this.getTeleportAnimation().setLooping(false);
    }

    /** Get the teleportoing status.
     * @return The teleportoing status.
     */
    public boolean isTeleporting() {
        return this.teleporting;
    }

    /** Set the teleportoing status.
     * @param teleportStatus The teleporting status.
     */
    private void setTeleportingStatus(boolean teleportStatus) {
        this.teleporting = teleportStatus;
    }

    /** Get the number of times the animation is shown, for each teleport action.
     * @return The teleport count.
     */
    private int getTeleportCount() {
        return this.teleportCount;
    }

    /** Set the number of times the animation is shown, for each teleport action.
     * @param count The teleport count.
     */
    private void setTeleportCount(int count) {
        this.teleportCount = count;
    }

    /** Get the player's inventory.
     * @return inventory.
     */
    public ArrayList<Item> getInventory() {
        return this.inventory;
    }

    /** Get the villagers dialog.
     * @return The dialog.
     */
    private String getDialogue() {
        return this.dialog;
    }

    /** Set the Unit dialog.
     * @param dialog The villager dialog.
     */
    // TODO
    protected void setDialogue(String dialog) {
        this.dialog = dialog;
    }

    /** Get the status if the dialog is to be rendered.
     * @return The the status.
     */
    // TODO
    protected boolean isShowUnitDialogue() {
        return this.showUnitDialogue;
    }

    /** Set the status if the dialog is to be rendered.
     * @param showUnitDialogue The villager dialog.
     */
    private void setShowUnitDialogue(boolean showUnitDialogue) {
        this.showUnitDialogue = showUnitDialogue;
    }

    /** Get the status if the dialog has been already rendered.
     * @return The the status.
     */
    // TODO
    protected boolean isDialogueShown() {
        return this.dialogShown;
    }

    /** Set the status if the dialog has been already rendered.
     * @param dialogShown The status.
     */
    // TODO
    protected void setDialogueShown(boolean dialogShown) {
        this.dialogShown = dialogShown;
    }

    /** Get the timer value of the dialog.
     * @return The dialog timer value.
     */
    // TODO
    protected int getDialogueTimer() {
        return dialogTimer;
    }

    /** Set the timer value of the dialog.
     * @param dialogTimer The dialog timer value.
     */
    // TODO
    protected void setDialogueTimer(int dialogTimer) {
        this.dialogTimer = dialogTimer;
    }

    /** Get the timer value of idling.
     * @return The dialog timer value.
     */
    private int getIdleTimer() {
        return this.idleTimer;
    }

    /** Set the timer value of idling.
     * @param idlingTimer The idling timer value.
     */
    private void setIdleTimer(int idlingTimer) {
        this.idleTimer = idlingTimer;
        if (idlingTimer == 0) {
            this.setIdleMax(IDLE_TIME + Math.abs(getRandomInt()) % 5 * 1000);
        }
    }

    private Animation getLeftAnimation() {
        return this.left;
    }

    private void setLeftAnimation(Image image) {
        this.left = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.left.setLooping(true);
        this.left.setAutoUpdate(false);
    }

    private Animation getRightAnimation() {
        return this.right;
    }

    private void setRightAnimation(Image image) {
        this.right = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.right.setLooping(true);
        this.right.setAutoUpdate(false);
    }

    private Animation getUpAnimation() {
        return this.up;
    }

    private void setUpAnimation(Image image) {
        this.up = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.up.setLooping(true);
        this.up.setAutoUpdate(false);
    }

    private Animation getDownAnimation() {
        return this.down;
    }

    private void setDownAnimation(Image image) {
        this.down = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.down.setLooping(true);
        this.down.setAutoUpdate(false);
    }

    private Animation getStandLeftAnimation() {
        return this.stnLeft;
    }

    private void setStandLeftAnimation(Image image) {
        this.stnLeft = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.stnLeft.setLooping(true);
        this.stnLeft.setAutoUpdate(false);
    }

    private Animation getStandRightAnimation() {
        return this.stnRight;
    }

    private void setStandRightAnimation(Image image) {
        this.stnRight = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.stnRight.setLooping(true);
        this.stnRight.setAutoUpdate(false);
    }

    private Animation getStandUpAnimation() {
        return this.stnUp;
    }

    private void setStandUpAnimation(Image image) {
        this.stnUp = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.stnUp.setLooping(true);
        this.stnUp.setAutoUpdate(false);
    }

    private Animation getStandDownAnimation() {
        return this.stnDown;
    }

    private void setStandDownAnimation(Image image) {
        this.stnDown = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.stnDown.setLooping(true);
        this.stnDown.setAutoUpdate(false);
    }

    /** Get the die animation.
     * @return The die animation.
     */
    private Animation getAttackLeftAnimation() {
        return this.attackLeft;
    }

    /** Set the die animation.
     * @param image The image used for the sprite sheet.
     */
    private void setAttackLeftAnimation(Image image) {
        this.attackLeft = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.attackLeft.setLooping(true);
        this.attackLeft.setAutoUpdate(false);
    }

    /** Get the die animation.
     * @return The die animation.
     */
    private Animation getAttackRightAnimation() {
        return this.attackRight;
    }

    /** Set the die animation.
     * @param image The image used for the sprite sheet.
     */
    private void setAttackRightAnimation(Image image) {
        this.attackRight = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.attackRight.setLooping(true);
        this.attackRight.setAutoUpdate(false);
    }

    /** Get the die animation.
     * @return The idling animation.
     */
    private Animation getIdlingAnimation() {
        return this.idling;
    }

    /** Set the die animation.
     * @param image The image used for the sprite sheet.
     */
    private void setIdlingAnimation(Image image) {
        this.idling = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.idling.setLooping(true);
        this.idling.setAutoUpdate(false);
    }

    /** Get the die animation.
     * @return The idling animation.
     */
    private Animation getUpgradeIdlingAnimation() {
        return this.upgIdling;
    }

    /** Set the die animation.
     * @param image The image used for the sprite sheet.
     */
    private void setUpgradeIdlingAnimation(Image image) {
        this.upgIdling = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.upgIdling.setLooping(true);
        this.upgIdling.setAutoUpdate(false);
    }

    /** Get the die animation.
     * @return The die animation.
     */
    private Animation getDieRightAnimation() {
        return this.dieRight;
    }

    /** Set the die animation.
     * @param image The image used for the sprite sheet.
     */
    private void setDieRightAnimation(Image image) {
        this.dieRight = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.dieRight.setLooping(false);
        this.dieRight.setAutoUpdate(false);
    }

    /** Get the die animation.
     * @return The die animation.
     */
    private Animation getDieLeftAnimation() {
        return this.dieLeft;
    }

    /** Set the die animation.
     * @param image The image used for the sprite sheet.
     */
    private void setDieLeftAnimation(Image image) {
        this.dieLeft = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.dieLeft.setLooping(false);
        this.dieLeft.setAutoUpdate(false);
    }

    private Animation getUpgradeLeftAnimation() {
        return this.upgLeft;
    }

    private void setUpgradeLeftAnimation(Image image) {
        this.upgLeft = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgLeft.setLooping(true);
        this.upgLeft.setAutoUpdate(false);
    }

    private Animation getUpgradeRightAnimation() {
        return this.upgRight;
    }

    private void setUpgradeRightAnimation(Image image) {
        this.upgRight = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgRight.setLooping(true);
        this.upgRight.setAutoUpdate(false);
    }

    private Animation getUpgradeUpAnimation() {
        return this.upgUp;
    }

    private void setUpgradeUpAnimation(Image image) {
        this.upgUp = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgUp.setLooping(true);
        this.upgUp.setAutoUpdate(false);
    }

    private Animation getUpgradeDownAnimation() {
        return this.upgDown;
    }

    private void setUpgradeDownAnimation(Image image) {
        this.upgDown = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgDown.setLooping(true);
        this.upgDown.setAutoUpdate(false);
    }

    private Animation getUpgradeStandLeftAnimation() {
        return this.upgStnLeft;
    }

    private void setUpgradeStandLeftAnimation(Image image) {
        this.upgStnLeft = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgStnLeft.setLooping(true);
        this.upgStnLeft.setAutoUpdate(false);
    }

    private Animation getUpgradeStandRightAnimation() {
        return this.upgStnRight;
    }

    private void setUpgradeStandRightAnimation(Image image) {
        this.upgStnRight = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgStnRight.setLooping(true);
        this.upgStnRight.setAutoUpdate(false);
    }

    private Animation getUpgradeStandUpAnimation() {
        return this.upgStnUp;
    }

    private void setUpgradeStandUpAnimation(Image image) {
        this.upgStnUp = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgStnUp.setLooping(true);
        this.upgStnUp.setAutoUpdate(false);
    }

    private Animation getUpgradeStandDownAnimation() {
        return this.upgStnDown;
    }

    private void setUpgradeStandDownAnimation(Image image) {
        this.upgStnDown = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_FAST_INTERVAL);
        this.upgStnDown.setLooping(true);
        this.upgStnDown.setAutoUpdate(false);
    }

    private Animation getUpgradeAttackLeftAnimation() {
        return upgAttackLeft;
    }

    private void setUpgradeAttackLeftAnimation(Image image) {
        this.upgAttackLeft = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.upgAttackLeft.setLooping(true);
        this.upgAttackLeft.setAutoUpdate(false);
    }

    private Animation getUpgradeAttackRightAnimation() {
        return this.upgAttackRight;
    }

    private void setUpgradeAttackRightAnimation(Image image) {
        this.upgAttackRight = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.upgAttackRight.setLooping(false);
        this.upgAttackRight.setAutoUpdate(false);
    }

    private Animation getUpgradeDieRightAnimation() {
        return this.upgDieRight;
    }

    private void setUpgradeDieRightAnimation(Image image) {
        this.upgDieRight = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.upgDieRight.setLooping(false);
        this.upgDieRight.setAutoUpdate(false);
    }

    private Animation getUpgradeDieLeftAnimation() {
        return this.upgDieLeft;
    }

    private void setUpgradeDieLeftAnimation(Image image) {
        this.upgDieLeft = new Animation(new SpriteSheet(image, this.getFrameWidth(), this.getFrameHeight()), FRAME_SLOW_INTERVAL);
        this.upgDieLeft.setLooping(true);
        this.upgDieLeft.setAutoUpdate(false);
    }

    private boolean isUpgraded() {
        return this.upgrade;
    }

    private void setUpgradeStatus(boolean upgrade) {
        this.upgrade = upgrade;
    }

    private Animation getAddonAnimation() {
        return this.addon;
    }

    public void setAddonAnimation(Animation addon) {
        this.addon = addon;
    }

    private boolean isUsingAddon() {
        return this.usingAddon;
    }

    public final void setAddonStatus(boolean addonStatus) {
        this.usingAddon = addonStatus;
    }

    protected boolean isAttacking() {
        return this.attacking;
    }

    protected final void setAttackingStatus(boolean attacking) {
        this.attacking = attacking;
    }

    // TODO
    protected boolean isUsingUpgrade() {
        return (this.usingUpg && this.isUpgradable() && this.isUpgraded());
    }

    public final void setUsingUprageStatus(boolean usingUpg) {
        boolean old = this.isUsingUpgrade();
        this.usingUpg = usingUpg;
        this.setAttackType(this.isUsingUpgrade() ? AttackType.ARCHER : AttackType.GROUND);
        if (old != usingUpg) {
            this.standInDefaultDirection();
        }
    }

    protected boolean isDieing() {
        return this.dieing;
    }

    private void setDieingStatus(boolean dieing) {
        this.dieing = dieing;
    }

    private AttackType getAttackType() {
        return this.attackType;
    }

    public final void setAttackType(AttackType type) {
        this.attackType = type;
    }

    private boolean isAutoWeaponChanged() {
        return this.weapon;
    }

    protected final void setAutoWeaponChange(boolean weapon) {
        this.weapon = weapon;
    }

    private boolean isUpdateAnimationSet() {
        return this.updateAni;
    }

    private void setUpdateAnimation(boolean update) {
        this.updateAni = update;
    }

    protected double getTempX() {
        return tempX;
    }

    protected void setTempX(double tempX) {
        this.tempX = tempX;
    }

    protected double getTempY() {
        return tempY;
    }

    protected void setTempY(double tempY) {
        this.tempY = tempY;
    }

    private int getIdleMax() {
        return this.idleMax;
    }

    private void setIdleMax(int idle) {
        this.idleMax = idle;
    }

    private double getAutoHealRate() {
        return this.autoHealRate;
    }

    public void setAutoHealRate(double autoHealRate) {
        this.autoHealRate = (autoHealRate < 0) ? 0 : autoHealRate;
    }

    private double getAutoHealTimer() {
        return this.autoHealTimer;
    }

    private void setAutoHealTimer(double autoHealTimer) {
        this.autoHealTimer = autoHealTimer;
    }

    private boolean isAutoHealing() {
        return this.autoHeal;
    }

    public final void setAutoHealingStatus(boolean autoHeal) {
        this.autoHeal = autoHeal;
    }

    protected boolean isMoving() {
        return this.moving;
    }

    private void setMoving(boolean moving) {
        this.moving = moving;
    }

    private boolean isAppearing() {
        return this.appear;
    }

    private void setAppearing(boolean appear) {
        this.appear = appear;
    }

    public boolean getInjured(int hit, int time) {

        this.setHP(this.getHP() - hit);

        if (this.getHP() == 0) {
            this.setHP(0);
            this.die();
            return true;
        }
        return false;
    }

    private int getDeadTimer() {
        return deadTimer;
    }

    private void setDeadTimer(int deadTimer) {
        this.deadTimer = deadTimer;
    }

    private Image getBonesImage() {
        return ImageLoader.getGroundUnitBones();
    }

    protected Unit getInteractingUnit() {
        return this.interactingUnit;
    }

    public final void setInteractingUnit(Unit interactingUnit) {
        this.interactingUnit = interactingUnit;
    }

    protected ArrayList<Unit> getUnitInteractedBy() {
        return this.interactedBy;
    }

    public final void setUnitInteractedBy(ArrayList<Unit> interactedBy) {
        this.interactedBy = interactedBy;
    }

    // TODO
    protected boolean isInteracting() {
        return this.isAttacking();
    }

    private boolean isUpgradable() {
        return this.upgradable;
    }

    public final void setUpgradable(boolean upgradable) {
        this.upgradable = upgradable;
    }

    public int getRestrictedTerrain() {
        return this.restrictedTerrain;
    }

    public final void setRestrictedTerrain(int restrictedTerrain) {
        this.restrictedTerrain = restrictedTerrain;
    }

    public void restrictToCurrentTerrain(World world) {
        int x = (int) this.getX() / world.getMap().getTileWidth();
        int y = (int) this.getY() / world.getMap().getTileHeight();
        this.setRestrictedTerrain(world.getTerrainType()[x][y]);
    }

    public boolean isFlyingUnit() {
        return (this.getAttackType() == AttackType.AIR);
    }

    /** Checks if the unit can attack by range (projectile attacks).
     * @return The range attacker state of this unit.
     */
    protected boolean isRangedAttacker() {
        return ((this.getAttackType() == AttackType.ARCHER) || this.isFlyingUnit());
    }

    public boolean isKillable() {
        return this.killable;
    }

    public final void setKillable(boolean killable) {
        this.killable = killable;
    }

    public void setRace(Race race) {
        this.race = race;
    }

    public Race getRace() {
        return this.race;
    }

    private boolean isInRange(double dist2) {
        return ((dist2 <= Unit.Squared(CLOSE_PROXIMITY))
                || (this.isRangedAttacker() && dist2 <= Unit.Squared(PROJECTILE_RANGE)));
    }

    public int getDefence() {
        return this.defence;
    }

    public void setDefence(int defence) {
        this.defence = defence;
    }

    protected boolean isPursuingTarget() {
        return this.pursuingTarget;
    }

    private void setPursuingTarget(boolean pursuingTarget) {
        this.pursuingTarget = pursuingTarget;
    }

    protected boolean isTargetReached() {
        return this.targetReached;
    }

    private void setTargetReached(boolean targetReached) {
        this.targetReached = targetReached;
        if (targetReached) {
            this.setPursuingTarget(false);
        }
    }

    private double getTargetX() {
        return this.targetX;
    }

    private void setTargetX(double targetX) {
        this.targetX = targetX;
    }

    private double getTargetY() {
        return this.targetY;
    }

    private void setTargetY(double targetY) {
        this.targetY = targetY;
    }

    public void setTargetToWalk(double x, double y) {

        this.setTargetX(x);
        this.setTargetY(y);
        this.setTargetReached(false);
        this.setPursuingTarget(true);

    }

    public void walkToTarget(int delta) {

        double dist, dist2 = 1;
        double dirx, diry;
        double dx = 0, dy = 0;

        Path path = this.getPath(this.getWorld(), this.getTargetX(), this.getTargetY());

        if (path != null && path.getLength() < 10) {

            double halfTileX = this.getWorld().getMap().getTileWidth() / 2;
            double halfTileY = this.getWorld().getMap().getTileHeight() / 2;

            dx = path.getStep(path.getLength() - 1).getX() * this.getWorld().getMap().getTileWidth() - this.getX();
            dy = path.getStep(path.getLength() - 1).getY() * this.getWorld().getMap().getTileHeight() - this.getY();

//			dx += (dx<0?-halfTileX:halfTileX);
//			dy += (dy<0?-halfTileY:halfTileY);
            dx += halfTileX;
            dy += halfTileY;

            dist2 = calculateDistanceSqrd(dx, dy);

        }

        dist = (float) Math.sqrt(dist2);
        dirx = dx / dist * this.getSpeed();
        diry = dy / dist * this.getSpeed();

        this.move(dirx * delta, diry * delta);

    }

    private boolean isReached() {

        double offset = 10;

        double minX = this.getTargetX() - offset;
        double maxX = this.getTargetX() + offset;
        double minY = this.getTargetY() - offset;
        double maxY = this.getTargetY() + offset;

        return (this.getX() >= minX && this.getX() <= maxX
                && this.getY() >= minY && this.getY() <= maxY);

    }

    public double getXP() {
        return this.xp;
    }

    public void setXP(double xp) {
        this.xp = xp;
    }

    public double getMaxXP() {
        return this.maxXP;
    }

    public void setMaxXP(double maxXP) {
        this.maxXP = maxXP;
    }

    public int getLevel() {
        return this.level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public int getMaxLevel() {
        return this.maxLevel;
    }

    public void setMaxLevel(int maxLevel) {
        this.maxLevel = maxLevel;
    }

    private void missHit(int miss) {
        this.addNewMiss(miss, MISS_DURATION);
    }

    private void addNewMiss(int miss, int time) {
        this.getMisses().add(miss);
        this.getMissDurations().add(time);
    }

    private void removeMiss(int index) {
        this.getMisses().remove(this.getMisses().get(index));
        this.getMissDurations().remove(this.getMissDurations().get(index));
    }

    private ArrayList<Integer> getMisses() {
        return this.misses;
    }

    private void setMisses(ArrayList<Integer> misses) {
        this.misses = misses;
    }

    private ArrayList<Integer> getMissDurations() {
        return missDurations;
    }

    private void setMissDurations(ArrayList<Integer> missDurations) {
        this.missDurations = missDurations;
    }

    private void setInventory(ArrayList<Item> inventory) {
        this.inventory = inventory;
    }

    private void updateMisses(int delta) {
        for (int i = this.getMissDurations().size() - 1; i >= 0; i--) {
            int d = this.getMissDurations().get(i) - delta;
            if (d <= 0) {
                this.removeMiss(i);
            } else {
                this.getMissDurations().set(i, d);
            }
        }
    }

    /** Renders the unit's display name only.
     * @param g The current Slick graphics context.
     */
    public void renderMisses(Graphics g, double cameraOffsetX, double cameraOffsetY) {

        g.setFont(FontLoader.getFontSize11());
        double GAP = 70;

        for (int i = 0; i < this.getMisses().size(); i++) {

            int dur = this.getMissDurations().get(i);
            //String text = String.valueOf(this.getMisses().elementAt(i));
            String text = "MISS";
            float val = (float) dur / MISS_DURATION;
            float width = g.getFont().getWidth(text);
            float y = MISS_HEIGHT * (1 - val);
            g.setColor(new Color(1f, 1f, 1f, val));
            g.drawString(text, (float)(this.getX() - (width / 2) - cameraOffsetX), (float)(this.getY() - GAP - y - cameraOffsetY));

        }

    }

    private LoadScript getScript() {
        return this.script;
    }

    private void setScript(final String fileName) {
        this.script = new LoadScript(fileName);
    }

    private void createFromScript() {
        this.setScript(Unit.RESOURCE_PATH + Unit.SCRIPT_CREATE);
        this.getScript().runScriptFunction(Unit.FUNC_CREATE, this);
        //this.getScript().runScriptFunction("test", this);
        this.getScript().closeScript();
    }

    protected void activateScript(String dirPath, String unitName) {
        this.setScript(dirPath + unitName + ".lua");
    }

    protected void activateScript(String fileName) {
        this.setScript(fileName);
    }

    protected void initialiseFromScript() {
        this.getScript().runScriptFunction(Unit.FUNC_INIT, this);
    }

    public boolean isAlly(Unit unit1, Unit unit2) {
        return this.getWorld().getQuest().getDiplomacy(unit1, unit2) == Diplomacy.ALLY;
    }

    public boolean isAlly(Unit unit) {
        return this.isAlly(this, unit);
    }

    public boolean isEnemy(Unit unit1, Unit unit2) {
        return this.getWorld().getQuest().getDiplomacy(unit1, unit2) == Diplomacy.ENEMY;
    }

    public boolean isEnemy(Unit unit) {
        return this.isEnemy(this, unit);
    }

    public static boolean isPlayer(Unit unit) {
        return (unit instanceof Player);
    }

    public static boolean isVillager(Unit unit) {
        return (unit instanceof Villager);
    }

    public static void resetCount() {
        Unit.unitsCount = 0;
    }

    /**
     * @return the revive
     */
    public boolean isRevivingAllowed() {
        return this.revive;
    }

    /**
     * @param revive the revive to set
     */
    private void setReviving(boolean revive) {
        this.revive = revive;
    }

    /**
     * @return the reviveX
     */
    public double getReviveX() {
        return this.reviveX;
    }

    /**
     * @param reviveX the reviveX to set
     */
    private void setReviveX(double reviveX) {
        this.reviveX = reviveX;
    }

    /**
     * @return the reviveY
     */
    public double getReviveY() {
        return this.reviveY;
    }

    /**
     * @param reviveY the reviveY to set
     */
    private void setReviveY(double reviveY) {
        this.reviveY = reviveY;
    }

    public void makeUnitRevivableAt(double x, double y)
    {
        this.setReviving(true);
        this.setReviveTeleport(true);
        this.setReviveX(x);
        this.setReviveY(y);
    }
    
    public void makeUnitRevivable()
    {
        this.setReviving(true);
        this.setReviveTeleport(false);
    }

    /**
     * @return the reviveTeleport
     */
    public boolean isReviveAndTeleport() {
        return this.reviveTeleport;
    }

    /**
     * @param reviveTeleport the reviveTeleport to set
     */
    private void setReviveTeleport(boolean reviveTeleport) {
        this.reviveTeleport = reviveTeleport;
    }

}
